#include <types.h>
#include <conio.h>
#include <queue.h>
#include <list.h>

#include <arch.h>
#include <schedule.h>
#include <sys/unistd.h>
#include <sys/ipc.h>
#include <sys/event.h>

/* forward declarations */
static thread_t *msg_dequeue(queue_t *);
static void msg_enqueue(queue_t *, thread_t *);

static event_t *ipc_event;

int msg_send(ipc_t * obj, void *msg, size_t size)
{
    msg_hdr_t *hdr;
    thread_t *th;
    void *kmsg;			/* kernel addr */
    int rc;

    if (size < sizeof(msg_hdr_t))
        return -1;

    sched_lock();
    if (!ipc_invalid(obj)) {
        sched_unlock();
        return -2;
    }
    /*
     * a thread can not send a message when it is
     * already receiving from the target object.
     * It will obviously cause a deadlock.
     */
    if (obj == curth->obj_receive) {
        sched_unlock();
        return -3;		/* deadlock detected! */
    }
    /*
     * translate message address to the kernel linear
     * address.  So that a receiver thread can access
     * the message via kernel pointer. We can catch
     * the page fault here.
     */
    if ((kmsg = kmem_map(msg, size)) == NULL) {
	sched_unlock();

	return -4;
    }

    curth->msg_addr = kmsg;
    curth->msg_size = size;

    /*
     * The sender ID is filled in the message header
     * by the kernel. So, the receiver can trust it.
     */
    hdr = (struct msg_header *) kmsg;
    hdr->task = curtask;

    /*
     * If receiver already exists, wake it up.
     * The highest priority thread can get the message.
     */
    if (!queue_empty(&obj->recvq)) {
        th = msg_dequeue(&obj->recvq);
        sched_unsleep(th, 0);
    }
    /*
     * Sleep until we get a reply message.
     * Note: Do not touch any data in the object
     * structure after we wakeup. This is because the
     * target object may be deleted while we are sleeping.
     */
    curth->obj_send = obj;
    msg_enqueue(&obj->sendq, curth);
    rc = sched_sleep(&ipc_event);
    if (rc == SLEEP_IRQ)
        queue_remove(&curth->ipc_link);

    curth->obj_send = NULL;
    sched_unlock();

    /*
     * Check sleep result.
     */
    switch (rc) {
        case SLEEP_BREAK:
            return -1;		/* Receiver has been terminated */
        case SLEEP_INVALID:
            return -2;		/* Object has been deleted */
        case SLEEP_IRQ:
            return -3;		/* Exception */
        default:
            /* DO NOTHING */
            break;
    };

    return 0;
}

/*
 * Receive a message.
 *
 * A thread can receive a message from the object which was
 * created by any thread belongs to same task. If the message
 * has not reached yet, it blocks until any message comes in.
 *
 * The size argument specifies the "maximum" size of the message
 * buffer to receive. If the sent message is larger than this
 * size, the kernel will automatically clip the message to this
 * maximum buffer size.
 *
 * When a message is received, the sender thread is removed from
 * object's send queue. So, another thread can receive the
 * subsequent message from that object. This is important for
 * the multi-thread server which must receive multiple messages
 * simultaneously.
 */
int msg_receive(ipc_t * obj, void *msg, size_t size)
{
    thread_t *th = NULL;
    size_t len = 0;
    int rc, error = 0;

    /*if (!user_area(msg))
       return EFAULT; */
    sched_lock();

    if (!ipc_invalid(obj)) {
        sched_unlock();
        return -1;
    }
    if (obj->owner != curtask) {
        sched_unlock();
        return -2;
    }

    /*
     * Check if this thread finished previous receive
     * operation.  A thread can not receive different
     * messages at once.
     */
    if (curth->obj_receive) {
        sched_unlock();
        return -3;
    }
    curth->obj_receive = obj;

    /*
     * If no message exists, wait until message arrives.
     */
    while (queue_empty(&obj->sendq)) {
        /*
        * Block until someone sends a message.
        */
        msg_enqueue(&obj->recvq, curth);
        rc = sched_sleep(&ipc_event);
        if (rc != 0) {
            /*
            * Receive is failed due to some reasons.
            */
            switch (rc) {
                case SLEEP_INVALID:
                    error = -1;	/* Object has been deleted */
                    break;
                case SLEEP_IRQ:
                    queue_remove(&curth->ipc_link);
                    error = -2;	/* Got exception */
                    break;
                default:
                    arch_panic("msg_receive");
                    break;
            };

            curth->obj_receive = NULL;
            sched_unlock();
            return (error);
        }

        /*
        * Check the existence of the sender thread again.
        * Even if this thread is woken by the sender thread,
        * the message may be received by another thread.
        * This may happen when another high priority thread
        * becomes runnable before we receive the message.
        */
    };

    th = msg_dequeue(&obj->sendq);

    /*
     * Copy out the message to the user-space.
     */
    /* len = MIN(size, t->msgsize); */
    if (th->msg_size > size)
        len = th->msg_size;
    else
        len = size;
    if (len > 0) {
        if (copyout(th->msg_addr, msg, len)) {
            msg_enqueue(&obj->sendq, th);
            curth->obj_receive = NULL;
            sched_unlock();
            return -1;
        }
    }
    /*
     * Detach the message from the target object.
     */
    curth->sender = th;
    th->receiver = curth;

    sched_unlock();

    return (error);
}

/*
 * Send a reply message.
 *
 * The target object must be the object that we are receiving.
 * Otherwise, this function will be failed.
 */
int msg_reply(ipc_t * obj, void *msg, size_t size)
{
    thread_t *th = NULL;
    size_t len = 0;

/*	if (!user_area(msg))
		return EFAULT;*/

    sched_lock();

    if (!ipc_invalid(obj) || obj != curth->obj_receive) {
        sched_unlock();
        return -1;
    }
    /*
     * Check if sender still exists
     */
    if (curth->sender == NULL) {
        /* Clear receive state */
        curth->obj_receive = NULL;
        sched_unlock();
        return -1;
    }
    /*
     * Copy a message to the sender's buffer.
     */
    th = curth->sender;
/*len = MIN(size, t->msgsize);*/
    if (th->msg_size < size)
        len = th->msg_size;
    else
        len = size;
    if (len > 0) {
        if (copyin(msg, th->msg_addr, len)) {
            sched_unlock();
            return -1;
        }
    }
    /*
     * Wakeup sender with no error.
     */
    sched_unsleep(th, 0);
    th->receiver = NULL;

    /* Clear transmit state */
    curth->sender = NULL;
    curth->obj_receive = NULL;

    sched_unlock();

    return 0;
}

/*
 * Cancel pending message operation of the specified thread.
 * This is called when the thread is terminated.
 *
 * We have to handle the following conditions to prevent deadlock.
 *
 * If the terminated thread is sending a message:
 *  1. A message is already received.
 *     -> The receiver thread will reply to the invalid thread.
 *
 *  2. A message is not received yet.
 *     -> The thread remains in send queue of the object.
 *
 * When the terminated thread is receiving a message.
 *  3. A message is already sent.
 *     -> The sender thread will wait for reply forever.
 *
 *  4. A message is not sent yet.
 *     -> The thread remains in receive queue of the object.
 */
void msg_cancel(thread_t * th)
{
    sched_lock();

    if (th->obj_send != NULL) {
        if (th->receiver != NULL)
            th->receiver->sender = NULL;
        else
            queue_remove(&th->ipc_link);
    }

    if (th->obj_receive != NULL) {
        if (th->sender != NULL) {
            sched_unsleep(th->sender, SLEEP_BREAK);
            th->sender->receiver = NULL;
        } else
            queue_remove(&th->ipc_link);
    }

    sched_unlock();
}

/*
 * Abort all message operations relevant to the specified object.
 * This is called when the target object is deleted.
 */
void msg_abort(ipc_t * obj)
{
    queue_t *q = NULL;
    thread_t *th = NULL;

    sched_lock();
    /*
     * Force wakeup all threads in the send queue.
     */
    while (!queue_empty(&obj->sendq)) {
        q = dequeue(&obj->sendq);
        th = queue_entry(q, thread_t, ipc_link);
        sched_unsleep(th, SLEEP_INVALID);
    };
    /*
     * Force wakeup all threads waiting for receive.
     */
    while (!queue_empty(&obj->recvq)) {
        q = dequeue(&obj->recvq);
        th = queue_entry(q, thread_t, ipc_link);
        sched_unsleep(th, SLEEP_INVALID);
    };

    sched_unlock();
}

/*
 * Dequeue thread from the IPC queue.
 * The most highest priority thread will be chosen.
 */
static thread_t *msg_dequeue(queue_t * head)
{
    queue_t *q;
    thread_t *th, *top;

    q = queue_first(head);
    top = queue_entry(q, thread_t, ipc_link);

    while (!queue_end(head, q)) {
        th = queue_entry(q, thread_t, ipc_link);
        if (th->prio < top->prio)
            top = th;

        q = queue_next(q);
    };

    queue_remove(&top->ipc_link);

    return (top);
}

static void msg_enqueue(queue_t * head, thread_t * th)
{
    enqueue(head, &th->ipc_link);
}

void msg_init(void)
{
    event_init(&ipc_event, "ipc");
}
